/*
 * Alloy Analyzer 4 -- Copyright (c) 2006-2008, Felix Chang
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package edu.mit.csail.sdg.alloy4;

import java.io.InputStream;
import java.util.Timer;
import java.util.TimerTask;

/**
 * This provides a convenience wrapper around a Process object.
 *
 * <p> To launch a subprocess, simply write Subprocess.exec(timeout, args);
 *
 * <p><b>Thread Safety:</b>  Safe.
 */

public final class Subprocess {

    /** Timer used to schedule a timeout for the process. */
    private static final Timer stopper = new Timer();

    /** This field will store the program output (or "" if an error occurred) */
    private String stdout = null;

    /** This field will store an error message (or "" if no error occurred) */
    private String stderr = null;

    /** Constructor is private since we only allow the static method exec() to construct objects of this class. */
    private Subprocess() { }

    /**
     * Executes the given command line, wait for its completion, then return its output.
     * @param timeLimit - we will attempt to terminate the process after that many milliseconds have passed
     * @param p - preconstructed Process object
     */
    private static String exec (final long timeLimit, final Process p) throws Exception {
        final Subprocess pro = new Subprocess();
        final InputStream f1 = p.getInputStream(), f2 = p.getErrorStream();
        TimerTask stoptask = new TimerTask() {
            public void run() {
               synchronized(pro) { if (pro.stdout!=null && pro.stderr!=null) return; pro.stdout=""; pro.stderr="Error: timeout"; }
               p.destroy();
            }
        };
        synchronized(Subprocess.class) { stopper.schedule(stoptask, timeLimit); }
        new Thread(new Runnable() {
            public void run() {
               String err = null;
               try { if (f2.read()>=0) err="Error: stderr"; } catch(Throwable ex) { err="Error: "+ex; }
               synchronized(pro) { if (err!=null) {pro.stdout=""; pro.stderr=err;} else if (pro.stderr==null) pro.stderr=""; }
            }
        }).start();
        p.getOutputStream().close();
        StringBuilder output = new StringBuilder();
        byte[] buf = new byte[8192];
        while(true) {
            int n = f1.read(buf);
            if (n<0) break; else for(int i=0; i<n; i++) output.append((char)(buf[i]));
        }
        synchronized(pro) { if (pro.stdout==null) pro.stdout=output.toString(); }
        for(int i=0; i<10; i++) {
            synchronized(pro) { if (pro.stderr!=null) return pro.stderr + pro.stdout; }
            Thread.sleep(500);
        }
        return "Error: wait timeout";
    }

    /**
     * Executes the given command line, wait for its completion, then return its output.
     * @param timeLimit - we will attempt to terminate the process after that many milliseconds have passed
     * @param commandline - the command line
     */
    public static String exec (final long timeLimit, final String[] commandline) {
        Process p = null;
        try {
            p = Runtime.getRuntime().exec(commandline);
            return exec(timeLimit, p);
        } catch (Throwable ex) {
            return "Error: "+ex;
        } finally {
            if (p!=null) p.destroy();
        }
    }
}
